﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AgCIM.Tools.SpatialEncoding.Beidou
{
    public class BeidouDecoder
    {
        /// <summary>
        /// 根据编码解码得到位置信息
        /// </summary>
        /// <param name="code">编码</param>
        /// <param name="options">选项，目前有 isThreeD：是否是三维码</param>
        /// <returns>解码后的位置信息</returns>
        public static (PositionInfo, HeightInfo?) Decode(string code, Dictionary<string, bool> options = null)
        {
            if (options == null)
            {
                options = new Dictionary<string, bool>();
            }
            
            if (options.ContainsKey("isThreeD") && options["isThreeD"])
            {
                (List<string> PlanarCode, List<string> HeightCode) codeInfo = HandleCode(code);

                var planarCode = string.Join("", codeInfo.PlanarCode);
                var positionInfo = DePlanarBeidouCode(planarCode);
                var levelValue = positionInfo.LevelInfo.LevelValue[0];

                // 转换int类型可以剔除？
                var heightCode = codeInfo.HeightCode.Select(item => int.Parse(item)).ToList();
                var heightInfo = DeHeightFromHeightCode(string.Join("", heightCode), levelValue);

                return (positionInfo, heightInfo);
            }
            else
            {
                var positionInfo = DePlanarBeidouCode(code);
                return (positionInfo, null);
            }
        }

        /// <summary>
        /// 主要是处理字符串形式编码, 内部类
        /// </summary>
        /// <param name="code">对应编码</param>
        /// <returns>包含平面编码和平面高度编码的对象</returns>
        private static (List<string> PlanarCode, List<string> HeightCode) HandleCode(string code)
        {
            int len = code.Length;
            var planarCode = new List<string>();
            var heightCode = new List<string>();
            int i = 0;

            while (i < len)
            {
                if (i == 0)
                {
                    planarCode.Add(code[i].ToString());
                    heightCode.Add(code[i + 1].ToString());
                    i += 2;
                }
                else if (i == 2)
                {
                    var codeArray = code.Substring(1).ToCharArray();
                    int backIndex = Array.FindIndex(codeArray, x => !char.IsDigit(x));
                    planarCode.Add(code.Substring(2, backIndex));
                    heightCode.Add(code.Substring(5, backIndex - 1));
                    i = backIndex + 4;
                }
                else if (i == 7 && i <= len - 3)
                {
                    planarCode.Add(code.Substring(i, 2));
                    heightCode.Add(code.Substring(i + 2, 1));
                    i += 3;
                }
                else if (i == 10 && i <= len - 2)
                {
                    planarCode.Add(code[i].ToString());
                    heightCode.Add(code[i + 1].ToString());
                    i += 2;
                }
                else if (i == 12 && i <= len - 3)
                {
                    planarCode.Add(code.Substring(i, 2));
                    heightCode.Add(code.Substring(i + 2, 1));
                    i += 3;
                }
                else if (i == 15 && i <= len - 3)
                {
                    planarCode.Add(code.Substring(i, 2));
                    heightCode.Add(code.Substring(i + 2, 1));
                    i += 3;
                }
                else if (len >= i + 1 && i == 18)
                {
                    planarCode.Add(code[i].ToString());
                    heightCode.Add(code[i + 1].ToString());
                    i += 2;
                }
                else if (len >= i + 2)
                {
                    planarCode.Add(code.Substring(i, 2));
                    heightCode.Add(code[i + 2].ToString());
                    i += 3;
                }
                else
                {
                    // 如果没有匹配的情况，直接退出循环
                    break;
                }
            }

            return (planarCode, heightCode);
        }

        public static PositionInfo DePlanarBeidouCode(string code)
        {
            string letterStr = "ABCDEFGHIJKLMNOPQRSTUV";
            char[] letterIndex = letterStr.ToCharArray();

            if (code.Length < 4) return null;

            char[] codeArray = code.Substring(1).ToCharArray();
            int backIndex = Array.FindIndex(codeArray, x => !char.IsDigit(x));
            string firstCode = code.Substring(1, backIndex + 1);

            double[] position = new double[2];
            int level = 0;

            double firstPositionX = (Convert.ToInt32(firstCode.Substring(0, firstCode.Length - 1)) - 1) * 6 - 180;
            position[0] = firstPositionX < 0 ? firstPositionX + 6 : firstPositionX;

            if (code[0] == 'N')
            {
                position[1] = Array.IndexOf(letterIndex, firstCode[firstCode.Length - 1]) * 4;
            }
            else
            {
                position[1] = Array.IndexOf(letterIndex, firstCode[firstCode.Length - 1]) * 4 * -1;
            }

            level++;
            string secondCode = code.Substring(backIndex + 2, 2);
            position[0] += position[0] < 0 ? -0.5 * Convert.ToInt32(secondCode[0].ToString(), 16) : 0.5 * Convert.ToInt32(secondCode[0].ToString(), 16);
            position[1] += position[1] < 0 ? -0.5 * Convert.ToInt32(secondCode[1].ToString()) : 0.5 * Convert.ToInt32(secondCode[1].ToString());

            backIndex += 3;
            level++;

            if (backIndex > code.Length - 1)
                return new PositionInfo(level, new double[] { 0.5, 0.5}, position);

            string thirdCode = code.Substring(backIndex + 1, 1);
            int thirdCodeInt = Convert.ToInt32(thirdCode[0].ToString());
            position[0] += position[0] < 0 ? (15.0 / 60) * (thirdCodeInt - (thirdCodeInt / 2) * 2) * -1 : (15.0 / 60) * (thirdCodeInt - (thirdCodeInt / 2) * 2);
            position[1] += position[1] < 0 ? (10.0 / 60) * (thirdCodeInt / 2) * -1 : (10.0 / 60) * (thirdCodeInt / 2);

            backIndex += 2;
            level++;

            if (backIndex > code.Length - 1)
                return new PositionInfo(level, new double[] { 15.0 / 60, 10.0 / 60 }, position);

            string forthCode = code.Substring(backIndex, 2).ToLower();
            GetCodePosition(ref position, new int[] { Convert.ToInt32(forthCode[0].ToString(), 16), Convert.ToInt32(forthCode[1].ToString(), 16) }, new double[] { 1.0 / 60, 1.0 / 60 });

            backIndex += 2;
            level++;

            if (backIndex > code.Length - 1)
                return new PositionInfo(level, new double[] { 1.0 / 60, 1.0 / 60 }, position);

            string fifthCode = code.Substring(backIndex, 2).ToLower();
            GetCodePosition(ref position, new int[] { Convert.ToInt32(fifthCode[0].ToString(), 16), Convert.ToInt32(fifthCode[1].ToString(), 16) }, new double[] { 4.0 / 3600, 4.0 / 3600 });

            backIndex += 2;
            level++;

            if (backIndex > code.Length - 1)
                return new PositionInfo(level, new double[] { 4.0 / 3600, 4.0 / 3600 }, position);

            string sixthCode = code.Substring(backIndex, 1);
            int sixthCodeInt = Convert.ToInt32(sixthCode[0].ToString());
            position[0] += position[0] < 0 ? (2.0 / 3600) * (sixthCodeInt - (sixthCodeInt / 2) * 2) * -1 : (2.0 / 3600) * (sixthCodeInt - (sixthCodeInt / 2) * 2);
            position[1] += position[1] < 0 ? (2.0 / 3600) * (sixthCodeInt / 2) * -1 : (2.0 / 3600) * (sixthCodeInt / 2);

            backIndex += 1;
            level++;

            if (backIndex > code.Length - 1)
                return new PositionInfo(level, new double[] { 2.0 / 3600, 2.0 / 3600 }, position);

            string seventhCode = code.Substring(backIndex, 2);
            GetCodePosition(ref position, new int[] { Convert.ToInt32(seventhCode[0].ToString()), Convert.ToInt32(seventhCode[1].ToString()) }, new double[] { 1.0 / (4 * 3600), 1.0 / (4 * 3600) });

            backIndex += 2;
            level++;

            if (backIndex > code.Length - 1)
                return new PositionInfo(level, new double[] { 1.0 / (4 * 3600), 1.0 / (4 * 3600) }, position);

            string eighthCode = code.Substring(backIndex, 2);
            GetCodePosition(ref position, new int[] { Convert.ToInt32(eighthCode[0].ToString()), Convert.ToInt32(eighthCode[1].ToString()) }, new double[] { 1.0 / (32 * 3600), 1.0 / (32 * 3600) });

            backIndex += 2;
            level++;

            if (backIndex > code.Length - 1)
                return new PositionInfo(level, new double[] { 1.0 / (32 * 3600), 1.0 / (32 * 3600) }, position);

            string ninthCode = code.Substring(backIndex, 2);
            GetCodePosition(ref position, new int[] {Convert.ToInt32(ninthCode[0].ToString()), Convert.ToInt32(ninthCode[1].ToString()) }, new double[] { 1.0 / (256 * 3600), 1.0 / (256 * 3600) });

            backIndex += 2;
            level++;

            if (backIndex > code.Length - 1)
                return new PositionInfo(level, new double[] { 1.0 / (256 * 3600), 1.0 / (256 * 3600) }, position);

            string tenthCode = code.Substring(backIndex, 2);
            level++;
            GetCodePosition(ref position, new int[] {Convert.ToInt32(tenthCode[0].ToString()), Convert.ToInt32(tenthCode[1].ToString()) }, new double[] { 1.0 / (2048 * 3600), 1.0 / (2048 * 3600) });

            return new PositionInfo(level, new double[] { 1.0 / (2048 * 3600), 1.0 / (2048 * 3600) }, position);
        }

        /// <summary>
        /// 根据高度编码获取高度值
        /// </summary>
        /// <param name="code">高度编码</param>
        /// <param name="levelValue">层级值</param>
        /// <param name="increaseCode">增加的编码值，默认为0</param>
        /// <returns>高度信息</returns>
        public static HeightInfo DeHeightFromHeightCode(string code, double levelValue, int increaseCode = 0)
        {
            int heightN = 0;
            if (code is string)
            {
                BeidouSpatialEncodingHeightCode heightCode = new BeidouSpatialEncodingHeightCode();
                heightN = heightCode.ConvertHeightOxToCount(code.ToCharArray().Select(c => c - '0').ToArray());
            }

            if (increaseCode > 0)
            {
                heightN += increaseCode;
            }

            var heightInfo = ComputeHeightFromGround(heightN, levelValue);
            return heightInfo;
        }

        /// <summary>
        /// 计算从地面的高度
        /// </summary>
        /// <param name="moundCount">砖的数量</param>
        /// <param name="cellWidth">砖的宽度</param>
        /// <returns>高度信息</returns>
        private static HeightInfo ComputeHeightFromGround(int moundCount, double cellWidth)
        {
            const double EarthRadius = 6378137.0;
            const double c0 = Math.PI / 180.0;
            var totalHeight = (Math.Pow(1 + c0, moundCount * cellWidth) - 1) * EarthRadius;
            var perHeight = Math.Pow(1 + c0, (moundCount - 1) * cellWidth) * EarthRadius * (Math.Pow(1 + c0, cellWidth) - 1);
            var heightInfo = new HeightInfo
            {
                TopHeightFromTerrain = totalHeight,
                PerHeight = perHeight,
                CellCount = moundCount
            };
            return heightInfo;
        }

        /// <summary>
        /// 根据具体编码计算对应位置
        /// </summary>
        /// <param name="position">位置</param>
        /// <param name="codeNum">对应编码的xy</param>
        /// <param name="codeValue">对应划分网格的大小</param>
        private static void GetCodePosition(ref double[] position, int[] codeNum, double[] codeValue)
        {
            position[0] += position[0] < 0 ? codeNum[0] * codeValue[0] * -1 : codeNum[0] * codeValue[0];
            position[1] += position[1] < 0 ? codeNum[1] * codeValue[1] * -1 : codeNum[1] * codeValue[1];
        }
    }

    public class PositionInfo
    {
        public LevelInfo LevelInfo { get; set; }
        public double[] Position { get; set; }

        public PositionInfo(int level, double[] levelValue, double[] position)
        {
            LevelInfo = new LevelInfo(level, levelValue);
            Position = position;
        }
    }

    public class LevelInfo
    {
        public int Level { get; set; }
        public double[] LevelValue { get; set; }

        public LevelInfo(int level, double[] levelValue)
        {
            Level = level;
            LevelValue = levelValue;
        }
    }

    public class HeightInfo
    {
        public int CellCount { get; set; }
        public double PerHeight { get; set;}
        public double TopHeightFromTerrain { get; set; }
    }
}
